#ifndef GAMEENGINE_GEOMETRY_COORDINATESYSTEMS_TRANSFORMER2D_HPP
#define GAMEENGINE_GEOMETRY_COORDINATESYSTEMS_TRANSFORMER2D_HPP

#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=================================================================================
// Implementation of the Transformer2d class

//! Default constructor
/*!
 @note Class T should have a default constructor
 */
template <class T>
Transformer2d<T>::Transformer2d() {
	// Setup as identity
	matrix_[0][0] = (T)1;
	matrix_[0][1] = (T)0;
	matrix_[1][0] = (T)0;
	matrix_[1][1] = (T)1;
	matrix_[2][0] = (T)0;
	matrix_[2][1] = (T)0;
}

//! Constructor
/*!
 *  Builds a 2D transformer that converts points from1, from2 and from2 into points to1, to2 and to3
 *
 @param from1 The point to transform into to1 in the destination coordinate system
 @param from2 The point to transform into to2 in the destination coordinate system
 @param from3 The point to transform into to3 in the destination coordinate system
 @param to1 The point in which from1 should be transformed in the destination coordinate system
 @param to2 The point in which from2 should be transformed in the destination coordinate system
 @param to3 The point in which from3 should be transformed in the destination coordinate system
 @sa Function set_transformation is called by this constructor
 @note Class T should have a default constructor
 */
template <class T>
Transformer2d<T>::Transformer2d(const Point2d<T>& from1, const Point2d<T>& from2, const Point2d<T>& from3,
								const Point2d<T>& to1, const Point2d<T>& to2, const Point2d<T>& to3)
{
	set_transformation(from1, from2, from3, to1, to2, to3);
}

//! Copy constructor
/*!
 @param rhs The transformer to copy
 @note Class T should have a default constructor
 @note Class T should have an assignment operator
 */
template <class T>
Transformer2d<T>::Transformer2d(const Transformer2d& rhs) {
	// Copy the transformation matrix
	for ( int k = 0; k < 3; ++k ) {
		for ( int l = 0; l < 2; ++l ) {
			matrix_[k][l] = rhs.matrix_[k][l];
		}
	}
}

//! Destructor
/*!
 @note The destructor of class T should be public
 */
template <class T>
Transformer2d<T>::~Transformer2d() {}

//! Assignment operator
/*!
 @param rhs The transformer to copy
 @note Class T should have an assignment operator
 */
template <class T>
Transformer2d<T>& Transformer2d<T>::operator=(const Transformer2d& rhs) {
	if ( &rhs != this ) {
		// Copy the transformation matrix
		for ( int k = 0; k < 3; ++k ) {
			for ( int l = 0; l < 2; ++l ) {
				matrix_[k][l] = rhs.matrix_[k][l];
			}
		}
	}
	return *this;
}

//! Setup a two-dimensional transformation
/*!
 @param from1 The point to transform into to1 in the destination coordinate system
 @param from2 The point to transform into to2 in the destination coordinate system
 @param from3 The point to transform into to3 in the destination coordinate system
 @param to1 The point in which from1 should be transformed in the destination coordinate system
 @param to2 The point in which from2 should be transformed in the destination coordinate system
 @param to3 The point in which from3 should be transformed in the destination coordinate system
 @return True if such a transformer exists. If no such transformation can be setup, the transformer is not modified.
 @note Class T should have an assignment operator
 */
template <class T>
bool Transformer2d<T>::set_transformation(const Point2d<T>& from1, const Point2d<T>& from2, const Point2d<T>& from3,
								const Point2d<T>& to1, const Point2d<T>& to2, const Point2d<T>& to3)
{
	// Compute denominator
	T den =  -from1.y()*from2.x() + from1.x()*from2.y()
		    + from1.y()*from3.x() - from2.y()*from3.x()
			- from1.x()*from3.y() + from2.x()*from3.y();
	if ( den == (T)0 ) return false;

	// Setup matrix
	matrix_[0][0] = -(-from2.y()*to1.x() + from3.y()*to1.x() + from1.y()*to2.x() - from3.y()*to2.x() - from1.y()*to3.x() + from2.y()*to3.x()) / den;
	matrix_[1][0] =  (-from2.x()*to1.x() + from3.x()*to1.x() + from1.x()*to2.x() - from3.x()*to2.x() - from1.x()*to3.x() + from2.x()*to3.x()) / den;
	matrix_[2][0] = -(from2.y()*from3.x()*to1.x() - from2.x()*from3.y()*to1.x() - from1.y()*from3.x()*to2.x() + from1.x()*from3.y()*to2.x() + from1.y()*from2.x()*to3.x() - from1.x()*from2.y()*to3.x()) / den;
	matrix_[0][1] =  (from2.y()*to1.y() - from3.y()*to1.y() - from1.y()*to2.y() + from3.y()*to2.y() + from1.y()*to3.y() - from2.y()*to3.y()) / den;
	matrix_[1][1] =  (-from2.x()*to1.y() + from3.x()*to1.y() + from1.x()*to2.y() - from3.x()*to2.y() - from1.x()*to3.y() + from2.x()*to3.y()) / den;
	matrix_[2][1] =  (-from2.y()*from3.x()*to1.y() + from2.x()*from3.y()*to1.y() + from1.y()*from3.x()*to2.y() - from1.x()*from3.y()*to2.y() - from1.y()*from2.x()*to3.y() + from1.x()*from2.y()*to3.y()) / den;
	return true;
}

//! Add a translation of the given vector to the transformation matrix
/*!
 @param offset The translation vector
 @return Always return true
 */
template <class T>
bool Transformer2d<T>::translate(const Vector2d<T>& offset) {
	matrix_[2][0] += offset.x();
	matrix_[2][1] += offset.y();
	return true;
}

//! Accessor to the first column of the transformation matrix (read only)
/*!
 @return The first column of the transformation matrix
 */
template <class T>
Vector2d<T> Transformer2d<T>::column1() const {
	return Vector2d<T>(matrix_[0][0], matrix_[1][0]);
}

//! Accessor to the second column of the transformation matrix (read only)
/*!
 @return The second column of the transformation matrix
 */
template <class T>
Vector2d<T> Transformer2d<T>::column2() const {
	return Vector2d<T>(matrix_[0][1], matrix_[1][1]);
}

//! Accessor to the third column of the transformation matrix (read only)
/*!
 @return The third column of the transformation matrix
 */
template <class T>
Vector2d<T> Transformer2d<T>::column3() const {
	return Vector2d<T>(matrix_[0][2], matrix_[1][2]);
}

//! Accessor to the first row of the transformation matrix (read only)
/*!
 @return The first row of the transformation matrix
 */
template <class T>
Vector3d<T> Transformer2d<T>::row1() const {
	return Vector3d<T>(matrix_[0][0], matrix_[0][1], matrix_[0][2]);
}

//! Accessor to the second row of the transformation matrix (read only)
/*!
 @return The second row of the transformation matrix
 */
template <class T>
Vector3d<T> Transformer2d<T>::row2() const {
	return Vector3d<T>(matrix_[1][0], matrix_[1][1], matrix_[1][2]);
}

//! Accessor to the given index of the transformation matrix (read only)
/*!
 @return The given index of the transformation matrix
 */
template <class T>
T Transformer2d<T>::matrix(Transformer2D::IndexRow row, Transformer2D::IndexColumn column) const {
	return matrix_[row][column];
}

//! Accessor to the given index of the transformation matrix (read-write)
/*!
 @return A reference to the given index of the transformation matrix
 */
template <class T>
T& Transformer2d<T>::matrix(Transformer2D::IndexRow row, Transformer2D::IndexColumn column) {
	return matrix_[row][column];
}

//! Transform the given point from the source space to the destination space
/*!
 @param from The coordinates of the point to transform in the source space
 @return The coordinates of the "from" point in the destination space
 */
template <class T>
Point2d<T> Transformer2d<T>::transform(const Point2d<T>& from) const {
	return Point2d<T>(
		matrix_[0][0]*from.x() + matrix_[1][0]*from.y() + matrix_[2][0],
		matrix_[0][1]*from.x() + matrix_[1][1]*from.y() + matrix_[2][1]
	);
}

//! Create an inverse transformer, i.e., the transformer that converts points from the destination space to the source space
/*!
 @param ok If not nil, receives true if an inverse transformer exists and false otherwise
 @return The inverse transformer. If no such transformer exists, the identity transformer is returned
 */
template <class T>
Transformer2d<T> Transformer2d<T>::inverse(bool* ok) const {
	Point2d<T> p0(0,0);
	Point2d<T> p1(1,0);
	Point2d<T> p2(0,1);
	Transformer2d res;
	bool b = res.set_transformation(transform(p0),transform(p1),transform(p2),p0,p1,p2);
	if ( ok != nil ) *ok = b;
	return res;
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif

#endif
